package com.newflypig.jblog.service.impl;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;

import org.hibernate.criterion.DetachedCriteria;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projection;
import org.hibernate.criterion.ProjectionList;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;
import org.hibernate.sql.JoinType;
import org.hibernate.transform.Transformers;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;

import com.newflypig.jblog.dao.IArticleDAO;
import com.newflypig.jblog.dao.IBaseDAO;
import com.newflypig.jblog.dao.ITagDAO;
import com.newflypig.jblog.model.Article;
import com.newflypig.jblog.model.BlogCommon;
import com.newflypig.jblog.model.BlogConfig;
import com.newflypig.jblog.model.Menu;
import com.newflypig.jblog.model.Pager;
import com.newflypig.jblog.model.Tag;
import com.newflypig.jblog.service.IArticleService;
import com.newflypig.jblog.service.IMenuService;
import com.newflypig.jblog.service.ITagService;
import com.newflypig.jblog.util.BlogUtils;


@Service("articleService")
public class ArticleServiceImpl extends BaseServiceImpl<Article> implements IArticleService{

	@Autowired
	private IArticleDAO articleDao;
	
	@Autowired
	private ITagService tagService;
	
	@Autowired
	private IMenuService menuService;
	
	@Autowired
	private ITagDAO tagDao;
	
	@Autowired
	private BlogCommon blogCommon;
	
	@Override
	protected IBaseDAO<Article> getDao() {
		return this.articleDao;
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<Article> findAllDesc(){
		return this.articleDao.findAllDesc();
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<Article> findByArchiveId(Integer archiveId) {
		String name = "article";
		
		ProjectionList pList = Projections.projectionList();  
		pList.add(Projections.property(name + ".articleId").as("articleId"));  
		pList.add(Projections.property(name + ".title").as("title"));
		pList.add(Projections.property(name + ".type").as("type"));
		pList.add(Projections.property(name + ".url").as("url"));
		pList.add(Projections.property(name + ".urlName").as("urlName"));
		pList.add(Projections.property(name + ".state").as("state"));  
		pList.add(Projections.property(name + ".rate").as("rate"));
		pList.add(Projections.property(name + ".createDate").as("createDate"));
		pList.add(Projections.property(name + ".lastEditDate").as("lastEditDate"));
		
		return this.articleDao.findByDC(
				DetachedCriteria.forClass(Article.class,name)
					.setProjection(pList)
					.add(Restrictions.eq("archive", archiveId))
					.setResultTransformer(Transformers.aliasToBean(Article.class))
		);
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<Article> findWidgets() {
		return this.articleDao.findByDC(
				DetachedCriteria
				.forClass(Article.class)
				.add(Restrictions.ge("type", Article.TYPE_WIDGET))
				.addOrder(Order.asc("index"))
		);
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public Pager<Article> findPagerByMenu(Integer page, String menuName) {
		
		Pager<Article> pagerArticles = this.generatePage(
				DetachedCriteria.forClass(Article.class,"article")
				.setProjection(generateNormalProjection())
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.eq("article.state",Article.STATE_PUBLISH))
				.createAlias("menus","menu",JoinType.INNER_JOIN)
				.add(Restrictions.eq("menu.urlName", menuName))
				.addOrder(Order.desc("article.index"))
				.addOrder(Order.desc("article.createDate"))
				.setResultTransformer(Transformers.aliasToBean(Article.class)),
				
				DetachedCriteria.forClass(Article.class,"article")
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.eq("article.state",Article.STATE_PUBLISH))
				.createAlias("menus","menu",JoinType.INNER_JOIN)
				.add(Restrictions.eq("menu.urlName", menuName)),
				
				page , BlogConfig.ARTICLES_PER_PAGE, BlogConfig.ARTICLES_SHOW_PAGE
		);
		
		if(pagerArticles != null)
			this.loadTags(pagerArticles.getData());
		
		return pagerArticles;
	}
	
	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public Pager<Article> findPagerByTag(Integer page, String tagUrlName) {
		Pager<Article> pagerArticles = this.generatePage(
				DetachedCriteria.forClass(Article.class,"article")
				.setProjection(generateNormalProjection())
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.eq("article.state",Article.STATE_PUBLISH))
				.createAlias("tags","tag",JoinType.INNER_JOIN)
				.add(Restrictions.eq("tag.urlName", tagUrlName))
				.addOrder(Order.desc("article.index"))
				.addOrder(Order.desc("article.createDate"))
				.setResultTransformer(Transformers.aliasToBean(Article.class)),
				
				DetachedCriteria.forClass(Article.class,"article")
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.eq("article.state",Article.STATE_PUBLISH))
				.createAlias("tags","tag",JoinType.INNER_JOIN)
				.add(Restrictions.eq("tag.urlName", tagUrlName)),
				
				page , BlogConfig.ARTICLES_PER_PAGE, BlogConfig.ARTICLES_SHOW_PAGE
		);
		
		if(pagerArticles != null)
			this.loadTags(pagerArticles.getData());
		
		return pagerArticles;
	}
	
	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public Pager<Article> findPagerByArchive(Integer page, String archiveUrlName) {
		Pager<Article> pagerArticles = this.generatePage(
				DetachedCriteria.forClass(Article.class,"article")
				.setProjection(generateNormalProjection())
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.eq("article.state",Article.STATE_PUBLISH))
				.createAlias("archive", "archive")
				.add(Restrictions.eq("archive.urlName", archiveUrlName))
				.addOrder(Order.desc("article.index"))
				.addOrder(Order.desc("article.createDate"))
				.setResultTransformer(Transformers.aliasToBean(Article.class)),
				
				DetachedCriteria.forClass(Article.class,"article")
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.eq("article.state",Article.STATE_PUBLISH))
				.createAlias("archive", "archive")
				.add(Restrictions.eq("archive.urlName", archiveUrlName)),
				
				page , BlogConfig.ARTICLES_PER_PAGE, BlogConfig.ARTICLES_SHOW_PAGE
		);
		
		if(pagerArticles != null)
			this.loadTags(pagerArticles.getData());
		
		return pagerArticles;
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<Article> findHFArticlesByMenu(short type, String menuName) {
		return this.articleDao.findByDC(
			DetachedCriteria
			.forClass(Article.class, "article")
			.setProjection(Projections.property("article.html").as("html"))
			.add(Restrictions.eq("type", type))
			.createAlias("menus", "menu")
			.add(Restrictions.eq("menu.urlName", menuName))
			.addOrder(Order.desc("article.index"))
			.addOrder(Order.desc("article.createDate"))
			.setResultTransformer(Transformers.aliasToBean(Article.class))
		);
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public Article findByUNorId(String articleUNorId) {
		Integer articleId = null;
		try{
			articleId = Integer.parseInt(articleUNorId);
		}catch(NumberFormatException e){
			articleId = null;
		}
		List<Article> articleList = this.articleDao.findByDC(
			DetachedCriteria
			.forClass(Article.class, "article")
			.setProjection(generateNormalProjection())
			.add(Restrictions.or(
				Restrictions.eq("article.urlName", articleUNorId)
				,Restrictions.eq("article.articleId", articleId)
			))
			.add(Restrictions.eq("article.state", Article.STATE_PUBLISH))
			.setResultTransformer(Transformers.aliasToBean(Article.class))
		);
		
		if(!articleList.isEmpty())
			this.loadTags(articleList);
		
		return articleList.isEmpty() ? null : articleList.get(0);
	}
	
	/**
	 * 构造一个标准的Article结果集
	 * 包含articleId,title,urlName,createDate,html
	 * 适用于大部分前台页面数据
	 * @return
	 */
	private ProjectionList generateNormalProjection(){
		ProjectionList pList = Projections.projectionList();
		pList.add(Projections.property("article.articleId").as("articleId"));  
		pList.add(Projections.property("article.title").as("title"));
		pList.add(Projections.property("article.urlName").as("urlName"));
		pList.add(Projections.property("article.createDate").as("createDate"));
		pList.add(Projections.property("article.html").as("html"));
		
		return pList;
	}
	
	
	/**
	 * 手动加载Article 的 Tags
	 */
	private void loadTags(List<Article> articles){
		for(Article article : articles){
			article.setTags(new HashSet<Tag>(this.tagDao.findByDC(
				DetachedCriteria
				.forClass(Tag.class,"tag")
				.createAlias("tag.articles", "article")
				.add(Restrictions.eq("article.articleId",article.getArticleId()))
			)));
		}
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public Pager<Article> findPagerForAdmin(Integer page) {
		Pager<Article> pagerArticles = this.generatePage(
				DetachedCriteria.forClass(Article.class,"article")
				.setProjection(generateSimpleProjection())
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.or(
					Restrictions.eq("article.state", Article.STATE_EDIT)
					,Restrictions.eq("article.state", Article.STATE_PUBLISH)
				))
				.addOrder(Order.desc("article.index"))
				.addOrder(Order.desc("article.createDate"))
				.setResultTransformer(Transformers.aliasToBean(Article.class)),
				
				DetachedCriteria.forClass(Article.class,"article")
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.or(
					Restrictions.eq("article.state", Article.STATE_EDIT)
					,Restrictions.eq("article.state", Article.STATE_PUBLISH)
				)),
				
				page, BlogConfig.ADMIN_ARTICLES_PER_PAGE, BlogConfig.ADMIN_ARTICLES_SHOW_PAGE
		);
		
		return pagerArticles;
	}

	private Projection generateSimpleProjection() {
		ProjectionList pList = Projections.projectionList();
		pList.add(Projections.property("article.articleId").as("articleId"));  
		pList.add(Projections.property("article.title").as("title"));
		pList.add(Projections.property("article.createDate").as("createDate"));
		pList.add(Projections.property("article.rate").as("rate"));
		pList.add(Projections.property("article.state").as("state"));
		pList.add(Projections.property("article.urlName").as("urlName"));
		
		return pList;
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public Pager<Article> findPagerForAdminTrashBox(Integer page) {
		Pager<Article> pagerArticles = this.generatePage(
				DetachedCriteria.forClass(Article.class,"article")
				.setProjection(generateSimpleProjection())
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.eq("article.state", Article.STATE_DELETE))
				.addOrder(Order.desc("article.index"))
				.addOrder(Order.desc("article.createDate"))
				.setResultTransformer(Transformers.aliasToBean(Article.class)),
				
				DetachedCriteria.forClass(Article.class,"article")
				.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
				.add(Restrictions.eq("article.state",Article.STATE_DELETE)),
				
				page, BlogConfig.ADMIN_ARTICLES_PER_PAGE, BlogConfig.ADMIN_ARTICLES_SHOW_PAGE
		);
		
		return pagerArticles;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void deleteForeverById(Integer articleId) {
		this.articleDao.deleteForeverById(articleId);
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void ratePP(Integer articleId) {
		this.articleDao.ratePP(articleId);
	}
	
	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void update(Article article){
		article.setShowInList(false);
		if(!StringUtils.isEmpty(article.getMenusStr())){
			String[] menuTitleList = article.getMenusStr().split(",");
			List<Menu> menuList = this.menuService.findByTitles(menuTitleList);
			for(Menu menu : menuList){
				if(menu.getId() == 6){
					article.setShowInList(true);
					menuList.remove(menu);
					break;
				}
			}
			article.setMenus(new HashSet<Menu>(menuList));
		}
		
		article.setTitle( BlogUtils.html(article.getTitle()) );
		this.articleDao.update(article);
		
		//清空tags多对多关系
		this.tagService.cleanConnect(article.getArticleId());
		
		//重构tags多对多关系
		if(!StringUtils.isEmpty( article.getTagsStr() )){
			String tagsStr = BlogUtils.html( article.getTagsStr() );
			String[] tags  = tagsStr.split(",");
			List<String> tagListCurrent = this.tagService.findAllFromTable();
			for(String tagTitle : tags){
				if(BlogUtils.tagListContainsStr(tagListCurrent, tagTitle)){
					Tag tag = tagService.findByTitle(tagTitle);
					tagService.makeConnect(tag.getTagId(), article.getArticleId());
				}else{
					Tag tag = new Tag(tagTitle);
					Integer tagId =(Integer) tagService.save(tag);
					tagService.makeConnect(tagId, article.getArticleId());
				}
			}
		}
	}
	
	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void recovery(Integer articleId){
		this.articleDao.setState(articleId, Article.STATE_PUBLISH);
	}
	
	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public Integer add(Article article){
		article.setShowInList(false);
		if(!StringUtils.isEmpty(article.getMenusStr())){
			String[] menuTitleList = article.getMenusStr().split(",");
			List<Menu> menuList = this.menuService.findByTitles(menuTitleList);
			for(Menu menu : menuList){
				if(menu.getId() == 6){
					article.setShowInList(true);
					menuList.remove(menu);
					break;
				}
			}
			article.setMenus(new HashSet<Menu>(menuList));
		}
		
		article.setTitle( BlogUtils.html(article.getTitle()) );
		if(StringUtils.isEmpty(article.getUrlName()))
			article.setUrlName(null);
		
		Integer articleId = (Integer) this.save(article);
		
		if(!StringUtils.isEmpty(article.getTagsStr())){
			String tagsStr = BlogUtils.html(article.getTagsStr());
			String[] tags = tagsStr.split(",");
			List<String> tagListCurrent = this.tagService.findAllFromTable();
			for(String tagTitle : tags){
				if(BlogUtils.tagListContainsStr(tagListCurrent, tagTitle)){
					Tag tag = tagService.findByTitle(tagTitle);
					tagService.makeConnect(tag.getTagId(),articleId);
				}else{
					Tag tag = new Tag(tagTitle);
					Integer tagId =(Integer) tagService.save(tag);
					tagService.makeConnect(tagId, articleId);
				}
			}
		}
		
		this.articleDao.updateIndex(articleId);
		return articleId;
	}

	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<String> findArticleUrls() {
		ProjectionList pList = Projections.projectionList();
		pList.add(Projections.property("article.articleId").as("articleId"));  
		pList.add(Projections.property("article.urlName").as("urlName"));
		
		List<Article> articlesList =this.articleDao.findByDC(
			DetachedCriteria.forClass(Article.class, "article")
			.setProjection(pList)
			.add(Restrictions.eq("article.state", Article.STATE_PUBLISH))
			.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
			.addOrder(Order.desc("article.index"))
			.addOrder(Order.desc("article.createDate"))
			.setResultTransformer(Transformers.aliasToBean(Article.class))
		);
		
		List<String> urlsList = new ArrayList<String>();
		articlesList.stream().forEach(article -> {
			urlsList.add(
				"http://" + this.blogCommon.getBlogUrl() + "/article/show/" + 
				( StringUtils.isEmpty(article.getUrlName()) ? article.getArticleId().toString() : article.getUrlName() )
			);
		});
		
		return urlsList;
	}

	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void callNoResultableProc(String procName) {
		this.articleDao.callNoResultableProc(procName);
	}
	
	private Projection generateRSSProjection() {
		ProjectionList pList = Projections.projectionList();
		pList.add(Projections.property("article.articleId").as("articleId"));  
		pList.add(Projections.property("article.title").as("title"));
		pList.add(Projections.property("article.createDate").as("createDate"));
		pList.add(Projections.property("article.urlName").as("urlName"));
		
		return pList;
	}
	
	//id,urlName,title,html,createDate,tags
	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<Article> findAllForRSS() {
		List<Article> list = this.articleDao.findForRSS(
			DetachedCriteria.forClass(Article.class,"article")
			.setProjection(generateRSSProjection())
			.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
			.add(Restrictions.eq("article.state", Article.STATE_PUBLISH))
			.addOrder(Order.desc("article.index"))
			.addOrder(Order.desc("article.createDate"))
			.setResultTransformer(Transformers.aliasToBean(Article.class))
		);
		
		this.loadTags(list);
		
		return list;
	}
	
	@Override
	@Transactional(propagation = Propagation.REQUIRED)
	public void exchangeIndex(Integer theId, Integer otherId) {
		this.articleDao.exchangeIndex(theId, otherId);
	}
	
	//id,urlName,title,createDate
	@Override
	@Transactional(propagation = Propagation.NOT_SUPPORTED, readOnly = true)
	public List<Article> findAllForList() {
		return this.articleDao.findByDC(
			DetachedCriteria.forClass(Article.class,"article")
			.setProjection(generateListProjection())
			.add(Restrictions.eq("article.type", Article.TYPE_ARTICLE))
			.add(Restrictions.eq("article.state", Article.STATE_PUBLISH))
			.add(Restrictions.eq("article.showInList", true))
			.addOrder(Order.desc("article.index"))
			.addOrder(Order.desc("article.createDate"))
			.setResultTransformer(Transformers.aliasToBean(Article.class)));
	}
	
	private Projection generateListProjection() {
		ProjectionList pList = Projections.projectionList();
		pList.add(Projections.property("article.articleId").as("articleId"));  
		pList.add(Projections.property("article.title").as("title"));
		pList.add(Projections.property("article.createDate").as("createDate"));
		pList.add(Projections.property("article.urlName").as("urlName"));
		
		return pList;
	}
}
